<template>
  <div
    ref="tableBox"
    class="MyTable"
  >
  <el-table
    ref="MyTable"
    v-loading="tableLoading"
    v-bind="$attrs"
    :data="dataSource"
    border
    stripe
    fit
    highlight-current-row
    :height="relHeight"
    :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
    :default-expand-all="isExpandAll"
    element-loading-text="数据加载中"
    element-loading-spinner="el-icon-loading"
    :row-class-name="tableRowClassName"
    :span-method="spanMethod"
    :row-key="getRowKey"
    :header-cell-style="{
        background: headerClass.headerBg,
        color: headerClass.headerColor
      }"
    @row-contextmenu="rowMenu"
    @selection-change="handleSelectionChange"
    :default-sort="defaultSort"
    @sort-change="changeTableSort"
  >
    <!-- 复选框-->
    <el-table-column
      v-if="showSelection"
      type="selection"
      align="center"
      width="55"
      fixed
      :reserve-selection="!!reserveSelection"
    />
    <el-table-column
      v-if="showIndex"
      label="序号"
      type="index"
      width="55"
      min-width="50"
      max-width="60"
      align="center"
    />
    <template v-for="(col, key) in columns || []">
      <!-- 操作列/自定义列 -->
      <slot v-if="col.slot" :name="col.prop || col.slot" />
      <el-table-column
        v-else-if="col.width != '0' && col.isHide != '1'"
        :key="key"
        :prop="col.prop"
        :sortable="col.sortable"
        :label="col.label"
        :fixed="col.fixed"
        :width="[col.width == null ? 'auto' : col.width]"
        :min-width="col.minWidth"
        :formatter="col.formatter"
        :align="col.align || 'left'"
      >
        <template slot-scope="scope">
          <!-- 正常渲染 -->
          <template v-if="!col.render">
            <template v-if="col.formatter">
              <span v-html="col.formatter(scope.row, col)" />
            </template>
            <template v-else>
              <span>{{ scope.row[col.prop] }}</span>
            </template>
          </template>
          <!-- render函数 -->
          <template v-else>
            {{ renderToHtml(col, scope.row) }}
            <slot :name="col.prop" />
          </template>
        </template>
      </el-table-column>
    </template>
    <!-- <template slot="empty">

    </template> -->
  </el-table>
  </div>
</template>

<script>
import request from '@/utils/request'
export default {
  name: 'MyTable',
  props: {
    // 表头颜色
    headerClass: {
      type: Object,
      default: () => {
        return {
          headerBg: '#dde3eb',
          headerColor: '#666666'
        }
      }
    },
    isExpandAll: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    rowTreeKey: {
      type: String,
      default: () => {
        return ''
      }
    },
    //是否显示树形表格
    // showTreeTable: {
    //   type: Boolean,
    //   default: () => {
    //     return false
    //   }
    // },
    stripe:{
      type: Boolean,
      default: () => {
        return true
      }
    },
    // 是否显示分页
    showPage: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    // 请求的url
    url: {
      type: String,
      default: () => {
        return ''
      }
    },
    // 请求参数
    params: {
      type: Object,
      default: () => {
        return {}
      }
    },
    // 是否需要复选框
    showSelection: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    // 请求类型
    requestType: {
      type: String,
      default: () => {
        return 'get'
      }
    },
    // 表格数据，为兼容在父组件请求能使用子组件的页码，所以传进来的数据要求是一个对象
    tableData: {
      type: Object,
      default: () => {
        return {}
      }
    },
    // 表格列
    columns: {
      type: Array,
      default: () => {
        return []
      }
    },
    // 表格高度
    height: {
      type: Number,
      default: () => {
        return null
      }
    },
    // 是否显示序号
    showIndex: {
      type: Boolean,
      default: () => {
        return true
      }
    },
    // 需要合并行的数组，传列的字段即可，如['name','title']
    spanProps: {
      type: Array,
      default: () => {
        return []
      }
    },
    loading: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    // 复选框选中的行
    selectRows: {
      type: Array,
      default: () => {
        return []
      }
    },
    // 每行的唯一字段
    rowKey: {
      type: [Number, String],
      default: () => {
        return null
      }
    },
    // 数据更新之后保留之前选中的数据（需指定 row-key）
    reserveSelection: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    // 数据结构支持传自定义的
    resConfig: {
      type: String,
      default: () => {
        return null
      }
    },
    // 数据结构字段支持传自定义的
    resName: {
      type: Object,
      default: () => {
        // return {
        //   data: 'data',
        //   total: 'total',
        //   pages: 'pages'
        // }
        return {}
      }
    },
    // 默认排序字段
    defaultSort: {
      type: Object,
      default: () => {
        return {}
      }
    }
  },
  data() {
    return {
      dataSource: this.tableData.data || [],
      tableLoading: this.loading,
      relHeight: this.height,
      multipleSelection: [],
      searchParams: {},
      resizeFn: null,
      page: {
        pageSize: 10,
        current: 1,
        pageSizes: [5,10, 20, 50, 100],
        total: 0
      }
    }
  },
  watch: {
    loading(val) {
      if (!this.url) {
        this.tableLoading = val
      }
    },
    tableData(val) {
      if (!this.url && this.isObject(val)) {
        // 父组件请求并且传过来的是一个对象
        this.dataSource = val.data
        this.$set(this.page, 'total', val.total) // 强制更新total
        this.setHeight()
      }
    },
    // 监听父组件传入的选中数据
    // 需要默认选中某些复选框时父组件传selectRows数组即可
    selectRows(val) {
      try {
        // 父组件选中的数组与子组件选中的数组比较，避免重复触发
        if (!this.equalsArr(val, this.multipleSelection)) {
          const oldSelectData = [...this.multipleSelection]
          this.$nextTick(async () => {
            await this.toggleSelection(val, oldSelectData)
          })
        }
      } catch (error) {
        console.log(
          '🉐 ~ file: index.vue ~ line 177 ~ selectRows ~ error',
          error
        )
      }
    }
  },

  updated() {
    // console.log('this.columns', this.columns)
  },
  mounted() {
    this.resizeFn = this.resize()
    this.$nextTick(() => {
      if (!this.height) {
        // 监听窗口变化
        window.addEventListener('resize', this.resizeFn)
      }
    })
  },

  // 组件卸载
  destroyed() {
    // 移除窗口监听
    window.removeEventListener('resize', this.resizeFn)
    this.resizeFn = null
  },

  methods: {
    // 监听浏览器高度变化
    resize() {
      let timer = null
      return () => {
        if (timer) {
          window.clearTimeout(timer)
        }
        timer = window.setTimeout(() => {
          this.setHeight()
        }, 100)
      }
    },

    // 给表格行赋值唯一标识
    getRowKey(row) {
      return row[this.rowKey] || row.id || row.rowIndex || row.index
    },

    // 给数据增加index,为 Table 中的某一行添加 class
    tableRowClassName({ row, rowIndex }) {
      row.rowIndex = rowIndex
      if (row.rowClass) {
        return row.rowClass
      }
    },

    rowMenu(row, column, event) {
      this.$emit('handRowMenu', {
        row: row,
        column: column,
        event: event
      })
    },

    onRowClick(row, column, event) {
      this.$emit('onRowClick', {
        row,
        column,
        event
      })
    },

    onAllSelected(selection) {
      this.$emit('onAllSelected', selection);
    },

    // 利用slot实现render函数jsx语法渲染
    renderToHtml(col, row) {
      if (typeof col.render === 'function') {
        this.$slots[col.prop] = [col.render(row)]
      }
    },

    // 复选框选择
    handleSelectionChange(val) {
      this.multipleSelection = val
      this.$emit('getSelectRows', val || [])
    },

    // 表格自适应高度
    setHeight() {
      this.relHeight = null
      this.$nextTick(() => {
        // this.relHeight = document.documentElement.clientHeight - 60 - 54;

        const MyTableRef = this.$refs.MyTable
        // 先清空上一次表格的高度再重新赋值，避免每次拿到的都是上一次设定的高度
        MyTableRef.$el.style.height = 'auto'
        if (this.height) {
          this.relHeight = this.height
        } else { // 父组件不传高度时表格高度自适应
          const tableBox = this.$refs.tableBox
          debugger
          const alHeight = tableBox.clientHeight
          const tableBoxTop = tableBox.offsetTop
          const bodyHeight = document.body.clientHeight
          const tableBoxBottom = bodyHeight - tableBoxTop
          const isOverHeight = !!((alHeight || 0) - (tableBoxBottom || 0) > 0)
          if (isOverHeight) { // 表格高度是否超出浏览器高度
            if(this.isShowSysButton){
              this.relHeight = this.showPage ? tableBoxBottom - 42 - 50 - 39 : tableBoxBottom - 50 - 39
            }else{
              this.relHeight = this.showPage ? tableBoxBottom - 42 - 50 : tableBoxBottom - 50
            }
          } else {
            if(this.isShowSysButton){
              this.relHeight = this.showPage ? alHeight - 42 - 39 : alHeight - 39
            }else{
              this.relHeight = this.showPage ? alHeight - 42 : alHeight
            }
          }
        }
      })
    },

    // 如果是在父组件请求又需要分页，则在父组件调用这个方法
    setPage(params = {}) {
      if (!this.isObject(params)) return // 判断是否是对象
      const paramsNew = {
        ...this.searchParams,
        page: this.page.current,
        size: this.page.pageSize,
        ...params
      }
      this.searchParams = { ...paramsNew }
      return paramsNew
    },

    // 切换每页展示条数
    handleSizeChange(val) {
      this.$set(this.page, 'current', 1)
      this.$set(this.page, 'pageSize', val)
      if (!this.url) {
        // 如果是在父组件请求
        const params = {
          ...this.searchParams,
          page: this.page.current,
          size: this.page.pageSize
        }
        // 如果是在父组件请求，抛出这个切换方法给父组件
        this.$emit('handlePageChange', params)
      } else {
        this.getList()
      }
    },

    // 切换页码
    handleCurrentChange(val) {
      this.$set(this.page, 'current', val)
      if (!this.url) {
        // 如果是在父组件请求
        const params = {
          ...this.searchParams,
          page: this.page.current,
          size: this.page.pageSize
        }
        // 如果是在父组件请求，抛出这个切换方法给父组件
        this.$emit('handlePageChange', params)
      } else {
        this.getList().then(res => {
          this.setHeight()
        })
      }
    },

    // 搜索
    search(param = {}) {
      this.$set(this.page, 'current', 1)
      this.searchParams = { ...this.searchParams, ...param }
      this.getList(param)
    },

    // 重置
    handleReset() {
      return new Promise((resolve, reject) => {
        this.$set(this.page, 'current', 1)
        this.$set(this.page, 'pageSize', 10)
        this.searchParams = {}
        this.getList().then(res => {
          resolve(res)
        })
      })
    },

    // 请求列表
    getList(param = {}) {
      return new Promise((resolve, reject) => {
        // 父组件可以传params，如果不传，可在调用getList方法时以参数形式传
        // param调用方法时传的参数
        // this.params通过调用组件传的参数
        // this.searchParams子组件用来缓存所有传进来的参数，方便切换页码时用到
        this.tableLoading = true
        this.dataSource = []
        this.searchParams = { ...this.searchParams, ...param }
        let paramsNew = Object.assign({}, this.params)
        paramsNew = { ...paramsNew, ...this.searchParams }

        // 是否需要分页
        if (this.showPage) {
          paramsNew.page = this.page.current
          paramsNew.size = this.page.pageSize
        }
        const paramsKey = { params: paramsNew }

        if (!['get', 'GET'].includes(this.requestType)) {
          // 非get请求时的处理
          paramsKey.data = paramsNew
          delete paramsKey.params
        }
        request({
          url: this.url,
          method: this.requestType || 'GET',
          ...paramsKey
        })
          .then(res => {
            this.tableLoading = false
            // 数据结构及字段支持传自定义的
            // :resConfig="res.data.data"  数据结构
            // :resName="{data:'data',total:'total',pages:'pages'}"  字段名称
            const option = this.resConfig && typeof this.resConfig === 'string'
            let newRes = res
            if (option) {
              const level = this.resConfig.split('.')
              if (level.length > 1) {
                level.map((item, index) => {
                  if (item !== 'res') {
                    newRes = newRes[item]
                  }
                })
              }
            }

            // ?.是可选链符的写法，其实props已经定义了this.resName的默认值为{}，可以不用可选链符判断，直接写成this.resName.data也可
            const records = res[this?.resName?.data] || res.records || []
            const total = res[this?.resName?.total] || res.total || 0
            const pages = res[this?.resName?.pages] || res.pages || 0

            if (pages) {
              this.$set(this.page, 'total', total) // 强制更新界面
              this.dataSource = option ? newRes || [] : records
              // this.setHeight()
              resolve(records)
            }
          })
          .catch(err => {
            this.tableLoading = false
            reject()
          })
      })
    },

    // 复选框默认选中处理
    async toggleSelection(val, oldSelectData) {
      // 提取rowKey数组
      const selectKeys = (val || []).map(item => item[this.rowKey] || item.id)
      // 解决操作删除标签时，上一页的数据无法被删除问题
      const deleteData = (oldSelectData || []).filter(
          item => !selectKeys.includes(item[this.rowKey])
        )
      ;(deleteData || []).map(item => {
        this.$refs.MyTable.toggleRowSelection(item, false)
      })
      // 调用toggleRowSelection方法会触发复选框change事件，选中的值会被改变，所以要合并数组
      let newDataSource = [...this.dataSource, ...val]
      newDataSource = this.unique(newDataSource, this.rowKey) // 去重
      ;(newDataSource || []).map(item => {
        const rowKey = item[this.rowKey] || item.id
        if (selectKeys.includes(rowKey)) {
          // 设置选中
          this.$refs.MyTable.toggleRowSelection(item, true)
        } else {
          // 取消选中
          this.$refs.MyTable.toggleRowSelection(item, false)
        }
      })
      return Promise.resolve()
    },

    // 合并行
    // 参数:dataSource-表格数据,spanProps-需要进行合并计算的字段列表,rowIndex-当前行数,columnIndex-当前列数,column-当前列
    spanMethodFunc(
      dataSource = [],
      spanProps = [],
      rowIndex,
      columnIndex,
      column
    ) {
      // if (columnIndex >= spanProps.length || !spanProps[columnIndex]) {
      if (!spanProps.includes(column.property)) {
        // 根据传入的字段列表,判断不需要合并的列
        return [1, 1]
      } else {
        // 使用try-catch,如果方法错误返回错误信息
        try {
          // let _spanProps = spanProps.slice(columnIndex, columnIndex + 1); //截取需要用到判断的字段名
          // 过滤出需要合并的当前列字段
          const _spanProps = spanProps.filter(item => item == column.property)
          // 判断是否从本行开始合并
          const merge = _spanProps.some(item => {
            // 如果当前行所需要判断合并的字段中有一个跟前一条数据不一样,本条数据即为合并的起点,第一条数据直接为合并起点
            return (
              rowIndex == 0 ||
              (item &&
                dataSource[rowIndex][item] != dataSource[rowIndex - 1][item])
            )
          })
          // 如果本条数据是合并起点,获取需要合并的数据条数
          if (merge) {
            const _list = dataSource.slice(rowIndex) // 截取从本条数据开始的列表
            // 获取合并行数
            const _lineNum = _list.findIndex((item, index) => {
              // 同merge判断,找到合并的终点
              return (
                index &&
                _spanProps.some(item1 => {
                  return item1 && item[item1] != _list[0][item1]
                })
              )
            })
            // 合并行数为-1时,输出_list的长度,否则输出_lineNum
            return [_lineNum === -1 ? _list.length : _lineNum, 1]
          } else {
            // 否则,返回[0,0],即本条数据被合并
            return [0, 0]
          }
        } catch (err) {
          console.error('spanMethodFunc error:', err)
        }
      }
    },

    // 合并行
    spanMethod({ row, column, rowIndex, columnIndex }) {
      return !this.spanProps || this.spanProps.length === 0
        ? false
        : this.spanMethodFunc(
          this.dataSource,
          this.spanProps,
          rowIndex,
          columnIndex,
          column
        )
    },
    // 改变Table某一列的背景颜色
    // columnIndex为列下标
    // columnbackgroundStyle({ row, column, rowIndex, columnIndex }) {
    //   if (columnIndex == 0) {
    //     // 让下标为1的列数背景颜色显示为红色（颜色自定义根据大家需求来）
    //     return 'background:#f9f9f9;'
    //   }
    // },
    changeTableSort() {
      this.$emit('changeTableSort')
    },

    clearSelection() {
      this.$refs.MyTable.clearSelection();
    },

    toggleAllSelection() {
      this.$refs.MyTable.toggleAllSelection();
    }
  }
}
</script>

<style scoped lang="scss">
@import '../../styles/my-table.scss';
.pagination-box {
  margin: 32px 0 10px 0;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  text-align: right;
}
.MyTable {
  width: 100%;
  height: 100%;
}
::v-deep .el-table .yellow{
  background-color: #fcfca6;
}
</style>
